/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.core.kernel.plugins;

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.StringTokenizer;

import org.jdpf.core.kernel.exceptions.BundleNotFoundException;
import org.jdpf.core.kernel.exceptions.CoreException;
import org.jdpf.core.kernel.exceptions.CoreExceptionsEnum;
import org.jdpf.core.kernel.logging.ILogger;
import org.jdpf.core.kernel.logging.LoggerFactory;
import org.jdpf.core.plugins.blocks.AbstractTransitionPlugin;
import org.jdpf.core.plugins.registration.RegistrationService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;

/**
 * It allows osgi services discovery and management.
 * 
 * @author <a href="mailto:farina.bruno@gmail.com">Bruno Farina</a>
 * @author <a href="mailto:pollizzio@gmail.com">Paolo Mauri</a>
 */
public class BundleToolBox {

	private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName";

	private static final String BUNDLE_NAME = "Bundle-Name";

	private static final String BUNDLE_CATEGORY = "Bundle-Category";

	private static final String JDPF_PACKAGES_PREFIX = "org.jdpf.";

	private static final ILogger logger = LoggerFactory.getDefault()
			.getLogger();

	private static BundleToolBox instance; // Singleton

	private BundleContext context;

	public BundleToolBox(BundleContext context) {
		this.setDefaultBundleContext(context);
		instance = this;
	}

	public static BundleToolBox getDefault() {
		return instance;
	}

	private void setDefaultBundleContext(BundleContext context) {
		this.context = context;
	}
	
	public AbstractTransitionPlugin getPlugin(String pluginId,
			String transitionId, String transitionName) {
		AbstractTransitionPlugin plugin = null;
		
		try {
			Bundle bundle = BundleToolBox.getDefault().getBundle(pluginId);
			bundle.start();
			ServiceReference[] refs = bundle.getRegisteredServices();
			boolean flag = true;
			ServiceReference ref;
			for (int j = 0; j < refs.length && flag; j++) {
				Object category;
				ref = refs[j];
				category = ref.getProperty("category");
				if ("registration".equals(category)) {
					Object service = context.getService(ref);
					RegistrationService rs = (RegistrationService) service;

					plugin = rs.newInstance(transitionId, transitionName);
					plugin.setPluginId(pluginId);
					ServiceRegistration sr = rs.getServiceRegistration(plugin.getServiceId());
					plugin.setCategory((String) sr.getReference().getProperty(
							"category"));
					plugin.setConverter(prepareConverter(transitionId, bundle
							.getHeaders()));
					plugin.setInputDataTypesList(prepareInputDataType(bundle
							.getHeaders()));
					plugin.setOutputDataTypes(prepareOutputDataType(bundle
							.getHeaders()));

					flag = false;
				}
			}
		} catch (Exception e) {
			logger.error("Unable to find the required bundle "+pluginId);
			logger.error(this, e.getMessage()+ " " + e.getClass());
		}

		return plugin;
	}

	private List<String> prepareOutputDataType(Dictionary headers)
			throws Exception {
		String outputData = (String) headers.get("Output-DataType");
		if (outputData != null) {
			StringTokenizer st = new StringTokenizer(outputData, ",");
			List<String> outputs = new ArrayList<String>(5);
			while (st.hasMoreTokens()) {
				outputs.add(st.nextToken());
			}
			return outputs;
		} else {
			throw new CoreException(CoreExceptionsEnum.IllegalManifestException,
					"Wrong manifest file format "
					+ "for plugin " + headers.get("Bundle-SymbolicName")
					+ ".\tMissing header Output-DataType.");
		}
	}

	private List<List<String>> prepareInputDataType(Dictionary headers)
			throws Exception {
		String inputData = (String) headers.get("Input-DataType");
		if (inputData != null) {
			StringTokenizer st = new StringTokenizer(inputData, ";");
			List<String> inputs = new ArrayList<String>(5);
			while (st.hasMoreTokens()) {
				inputs.add(st.nextToken());
			}
			List<List<String>> out = new ArrayList<List<String>>(5);
			StringTokenizer stt = new StringTokenizer(inputs.get(0), ",");
			for (int i = 0; i < stt.countTokens(); i++) {
				out.add(new ArrayList<String>(5));
			}
			for (int i = 0; i < inputs.size(); i++) {
				stt = new StringTokenizer(inputs.get(i), ",");
				int j = 0;
				while (stt.hasMoreTokens()) {
					out.get(j).add(stt.nextToken());
					j++;
				}
			}
			return out;
		} else {
			throw new CoreException(CoreExceptionsEnum.IllegalManifestException, "Wrong manifest file format "
					+ "for plugin " + headers.get("Bundle-SymbolicName")
					+ ".\n" + "Missing header Input-DataType.");
		}
	}

	private IConverter prepareConverter(String transitionId, Dictionary headers) {
		String converterName = (String) headers.get("Class-Converter");
		IConverter conv = null;
		if (converterName == null) {
			conv = new DefaultConverter(transitionId);
			//if (logging) logger.log(this, transitionId, "Using Default Converter");
		} else {
			try {
				conv = (IConverter) Class.forName(converterName)
						.getConstructor(null).newInstance(null);
				//if (logging) logger.log(this, transitionId, "Using the converter '"
					//	+ conv.getClass().getName() + "' ");
			} catch (Exception e) {
				logger.error("Unable to load the converter "+conv.getClass().getName() );
			    logger.error(this, e.getMessage());
			}
		}
		return conv;
	}

	public Bundle getBundle(String id) throws BundleNotFoundException {
		Bundle[] bundles = context.getBundles();
		boolean flag = true;
		Bundle bundle = null;

		for (int i = 0; i < bundles.length && flag; i++) {
			bundle = bundles[i];
			Dictionary dic = bundle.getHeaders();
			Object value = dic.get(BundleToolBox.BUNDLE_SYMBOLIC_NAME);
			if (id.equals(value)) {
				flag = false;
			}
		}
		if (flag) {
			throw new BundleNotFoundException(id);
		}
		return bundle;
	}

	public Bundle getBundleByName(String bundleName)
			throws BundleNotFoundException {
		boolean flag = true;
		Bundle bundle = null;
		Bundle[] bundles = context.getBundles();
		for (int i = 0; i < bundles.length && flag; i++) {
			bundle = bundles[i];
			if (bundleName.equals(bundle.getHeaders().get(
					BundleToolBox.BUNDLE_NAME)))
				flag = false;
		}

		if (flag) {
			throw new BundleNotFoundException(bundleName);
		}

		return bundle;
	}

	public Bundle[] getBundles(String category) {
		Bundle[] b = context.getBundles();
		List<Bundle> out = new ArrayList<Bundle>(10);
		for (int i = 0; i < b.length; i++) {
			if (b[i].getSymbolicName().startsWith(
					BundleToolBox.JDPF_PACKAGES_PREFIX)) {
				Object cat = b[i].getHeaders().get(
						BundleToolBox.BUNDLE_CATEGORY);
				if (cat != null && cat.toString().equals(category)) {
					out.add(b[i]);
				}
			}
		}
		Bundle[] outBundles = new Bundle[out.size()];
		for (int i = 0; i < out.size(); i++) {
			outBundles[i] = out.get(i);
		}
		return outBundles;
	}

	public Dictionary getHeaders(String id) {
		Dictionary dic = null;
		try {
			dic = getBundle(id).getHeaders();
		} catch (BundleNotFoundException e) {
			logger.fatal(e.getMessage());
		}

		return dic;
	}

	public void stopBundle(String id) {
		try {
			Bundle bundle = getBundle(id);
			bundle.stop();
		} catch (Exception e) {
			logger.fatal(e.getMessage());
		}
	}

	public void uninstallBundle(String id) {
		try {
			Bundle bundle = getBundle(id);
			bundle.uninstall();
		} catch (Exception e) {
			logger.fatal(e.getMessage());
		}
	}

	public Object getService(ServiceReference ref) {
		Object service = null;
		service = context.getService(ref);
		return service;
	}

	public Object getService(String serviceId) {
		ServiceReference sr = context.getServiceReference(serviceId);
		return context.getService(sr);
	}

	public String getBundleStatus(int status) {
		String retStr = "UNKNOWN";
		switch (status) {
		case 32:
			retStr = "ACTIVE";
			break;
		case 2:
			retStr = "INSTALLED";
			break;
		case 4:
			retStr = "RESOLVED";
			break;
		case 8:
			retStr = "STARTING";
			break;
		case 16:
			retStr = "STOPPING";
			break;
		case 1:
			retStr = "UNINSTALLED";
			break;
		}
		return retStr;
	}

	public String getBundleEventType(int type) {
		String retStr = "UNKNOWN";
		switch (type) {
		case 1:
			retStr = "INSTALLED";
			break;
		case 512:
			retStr = "LAZY ACTIVATION";
			break;
		case 32:
			retStr = "RESOLVED";
			break;
		case 2:
			retStr = "STARTED";
			break;
		case 128:
			retStr = "STARTING";
			break;
		case 4:
			retStr = "STOPPED";
			break;
		case 256:
			retStr = "STOPPING";
			break;
		case 16:
			retStr = "UNINSTALLED";
			break;
		case 64:
			retStr = "UNRESOLVED";
			break;
		case 8:
			retStr = "UPDATED";
			break;
		}
		return retStr;
	}

	public String getServiceEventType(int type) {
		String retStr = "UNKNOWN";
		switch (type) {
		case 1:
			retStr = "REGISTERED";
			break;
		case 2:
			retStr = "MODIFIED";
			break;
		case 4:
			retStr = "UNREGISTERING";
			break;
		}
		return retStr;
	}

	public String getFrameworkEventType(int type) {
		String retStr = "UNKNOWN";
		switch (type) {
		case 2:
			retStr = "ERROR";
			break;
		case 32:
			retStr = "INFO";
			break;
		case 4:
			retStr = "PACKAGES REFRESHED";
			break;
		case 1:
			retStr = "STARTED";
			break;
		case 8:
			retStr = "STARTLEVEL CHANGED";
			break;
		case 16:
			retStr = "WARNING";
			break;
		}
		return retStr;
	}
}
